<html>
<head>
<meta charset="utf-8"/>
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover" name="viewport"/>
<meta content="telephone=no" name="format-detection"/>
<style type="text/css">

#watermark {

  position: relative;
  overflow: hidden;
}

#watermark .x {
  position: absolute;
  top: 800;
  left: 400;
  color: #3300ff;
  font-size: 50px;
  pointer-events: none;
  opacity:0.5;
  filter:Alpha(opacity=50);
  -webkit-transform: rotate(45deg);
  -moz-transform: rotate(45deg);
}
    </style>
<style type="text/css">
 html{color:#333;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;text-rendering:optimizelegibility;font-family:Helvetica Neue,PingFang SC,Verdana,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif}html.borderbox *,html.borderbox :after,html.borderbox :before{box-sizing:border-box}article,aside,blockquote,body,button,code,dd,details,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hr,input,legend,li,menu,nav,ol,p,pre,section,td,textarea,th,ul{margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,menu,nav,section{display:block}audio,canvas,video{display:inline-block}body,button,input,select,textarea{font:300 1em/1.8 PingFang SC,Lantinghei SC,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,Helvetica,sans-serif}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}table{border-collapse:collapse;border-spacing:0}fieldset,img{border:0}blockquote{position:relative;color:#999;font-weight:400;border-left:1px solid #1abc9c;padding-left:1em;margin:1em 3em 1em 2em}@media only screen and (max-width:640px){blockquote{margin:1em 0}}abbr,acronym{border-bottom:1px dotted;font-variant:normal}abbr{cursor:help}del{text-decoration:line-through}address,caption,cite,code,dfn,em,th,var{font-style:normal;font-weight:400}ol,ul{list-style:none}caption,th{text-align:left}q:after,q:before{content:""}sub,sup{font-size:75%;line-height:0;position:relative}:root sub,:root sup{vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}a{color:#1abc9c}a:hover{text-decoration:underline}.typo a{border-bottom:1px solid #1abc9c}.typo a:hover{border-bottom-color:#555;color:#555}.typo a:hover,a,ins{text-decoration:none}.typo-u,u{text-decoration:underline}mark{background:#fffdd1;border-bottom:1px solid #ffedce;padding:2px;margin:0 5px}code,pre,pre tt{font-family:Courier,Courier New,monospace}pre{background:hsla(0,0%,97%,.7);border:1px solid #ddd;padding:1em 1.5em;display:block;-webkit-overflow-scrolling:touch}hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:.8em;height:10px}.typo-small,figcaption,small{font-size:.9em;color:#888}b,strong{font-weight:700;color:#000}[draggable]{cursor:move}.clearfix:after,.clearfix:before{content:"";display:table}.clearfix:after{clear:both}.clearfix{zoom:1}.textwrap,.textwrap td,.textwrap th{word-wrap:break-word;word-break:break-all}.textwrap-table{table-layout:fixed}.serif{font-family:Palatino,Optima,Georgia,serif}.typo-dl,.typo-form,.typo-hr,.typo-ol,.typo-p,.typo-pre,.typo-table,.typo-ul,.typo dl,.typo form,.typo hr,.typo ol,.typo p,.typo pre,.typo table,.typo ul,blockquote{margin-bottom:1rem}h1,h2,h3,h4,h5,h6{font-family:PingFang SC,Helvetica Neue,Verdana,Microsoft Yahei,Hiragino Sans GB,Microsoft Sans Serif,WenQuanYi Micro Hei,sans-serif;color:#000;line-height:1.35}.typo-h1,.typo-h2,.typo-h3,.typo-h4,.typo-h5,.typo-h6,.typo h1,.typo h2,.typo h3,.typo h4,.typo h5,.typo h6{margin-top:1.2em;margin-bottom:.6em;line-height:1.35}.typo-h1,.typo h1{font-size:2em}.typo-h2,.typo h2{font-size:1.8em}.typo-h3,.typo h3{font-size:1.6em}.typo-h4,.typo h4{font-size:1.4em}.typo-h5,.typo-h6,.typo h5,.typo h6{font-size:1.2em}.typo-ul,.typo ul{margin-left:1.3em;list-style:disc}.typo-ol,.typo ol{list-style:decimal;margin-left:1.9em}.typo-ol ol,.typo-ol ul,.typo-ul ol,.typo-ul ul,.typo li ol,.typo li ul{margin-bottom:.8em;margin-left:2em}.typo-ol ul,.typo-ul ul,.typo li ul{list-style:circle}.typo-table td,.typo-table th,.typo table caption,.typo table td,.typo table th{border:1px solid #ddd;padding:.5em 1em;color:#666}.typo-table th,.typo table th{background:#fbfbfb}.typo-table thead th,.typo table thead th{background:hsla(0,0%,95%,.7)}.typo table caption{border-bottom:none}.typo-input,.typo-textarea{-webkit-appearance:none;border-radius:0}.typo-em,.typo em,caption,legend{color:#000;font-weight:inherit}.typo-em{position:relative}.typo-em:after{position:absolute;top:.65em;left:0;width:100%;overflow:hidden;white-space:nowrap;content:"\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB\30FB"}.typo img{max-width:100%}.common-content{font-weight:400;color:#353535;line-height:1.75rem;white-space:normal;word-break:normal;font-size:1rem}.common-content img{display:block;max-width:100%;background-color:#eee}.common-content audio,.common-content video{width:100%;background-color:#eee}.common-content center,.common-content font{margin-top:1rem;display:inline-block}.common-content center{width:100%}.common-content pre{margin-top:1rem;padding-left:0;padding-right:0;position:relative;overflow:hidden}.common-content pre code{font-size:.8rem;font-family:Consolas,Liberation Mono,Menlo,monospace,Courier;display:block;width:100%;box-sizing:border-box;padding-left:1rem;padding-right:1rem;overflow-x:auto}.common-content hr{border:none;margin-top:1.5rem;margin-bottom:1.5rem;border-top:1px solid #f5f5f5;height:1px;background:none}.common-content b,.common-content h1,.common-content h2,.common-content h3,.common-content h4,.common-content h5,.common-content strong{font-weight:700}.common-content h1,.common-content h2{font-size:1.125rem;margin-bottom:.45rem}.common-content h3,.common-content h4,.common-content h5{font-size:1rem;margin-bottom:.45rem}.common-content p{font-weight:400;color:#353535;margin-top:.15rem}.common-content .orange{color:#ff5a05}.common-content .reference{font-size:1rem;color:#888}.custom-rich-content h1{margin-top:0;font-weight:400;font-size:15.25px;border-bottom:1px solid #eee;line-height:2.8}.custom-rich-content li,.custom-rich-content p{font-size:14px;color:#888;line-height:1.6}table.hljs-ln{margin-bottom:0;border-spacing:0;border-collapse:collapse}table.hljs-ln,table.hljs-ln tbody,table.hljs-ln td,table.hljs-ln tr{box-sizing:border-box}table.hljs-ln td{padding:0;border:0}table.hljs-ln td.hljs-ln-numbers{min-width:15px;color:rgba(27,31,35,.3);text-align:right;white-space:nowrap;cursor:pointer;user-select:none}table.hljs-ln td.hljs-ln-code,table.hljs-ln td.hljs-ln-numbers{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;line-height:20px;vertical-align:top}table.hljs-ln td.hljs-ln-code{position:relative;padding-right:10px;padding-left:10px;overflow:visible;color:#24292e;word-wrap:normal;white-space:pre}video::-webkit-media-controls{overflow:hidden!important}video::-webkit-media-controls-enclosure{width:calc(100% + 32px);margin-left:auto}.button-cancel{color:#888;border:1px solid #888;border-radius:3px;margin-right:12px}.button-cancel,.button-primary{-ms-flex-positive:1;flex-grow:1;height:35px;display:inline-block;font-size:15px;text-align:center;line-height:36px}.button-primary{color:#fff;background-color:#ff5a05;border-radius:3px}@font-face{font-family:iconfont;src:url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.eot);src:url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.eot#iefix) format("embedded-opentype"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.woff) format("woff"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.ttf) format("truetype"),url(//at.alicdn.com/t/font_372689_bwwwtosxtzp.svg#iconfont) format("svg")}@font-face{font-family:player-font;src:url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.eot);src:url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.eot#iefix) format("embedded-opentype"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.woff) format("woff"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.ttf) format("truetype"),url(//at.alicdn.com/t/font_509397_1cyjv4o90qiod2t9.svg#player-font) format("svg")}.iconfont{font-family:iconfont!important;font-size:16px;font-style:normal;-webkit-font-smoothing:antialiased;-webkit-text-stroke-width:.2px;-moz-osx-font-smoothing:grayscale}html{background:#fff;min-height:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{width:100%}body.fixed{overflow:hidden;position:fixed;width:100vw;height:100vh}i{font-style:normal}a{word-wrap:break-word;-webkit-tap-highlight-color:rgba(0,0,0,0)}a:hover{text-decoration:none}.fade-enter-active,.fade-leave-active{transition:opacity .3s}.fade-enter,.fade-leave-to{opacity:0}.MathJax,.MathJax_CHTML,.MathJax_MathContainer,.MathJax_MathML,.MathJax_PHTML,.MathJax_PlainSource,.MathJax_SVG{outline:0}.ios-app-switch .js-audit{display:none}._loading_wrap_{position:fixed;width:100vw;height:100vh;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999}._loading_div_class_,._loading_wrap_{display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center}._loading_div_class_{word-wrap:break-word;padding:.5rem .75rem;text-align:center;z-index:9999;font-size:.6rem;max-width:60%;color:#fff;border-radius:.25rem;-ms-flex-direction:column;flex-direction:column}._loading_div_class_ .message{color:#353535;font-size:16px;line-height:3}.spinner{animation:circle-rotator 1.4s linear infinite}.spinner *{line-height:0;box-sizing:border-box}@keyframes circle-rotator{0%{transform:rotate(0deg)}to{transform:rotate(270deg)}}.path{stroke-dasharray:187;stroke-dashoffset:0;transform-origin:center;animation:circle-dash 1.4s ease-in-out infinite,circle-colors 5.6s ease-in-out infinite}@keyframes circle-colors{0%{stroke:#ff5a05}to{stroke:#ff5a05}}@keyframes circle-dash{0%{stroke-dashoffset:187}50%{stroke-dashoffset:46.75;transform:rotate(135deg)}to{stroke-dashoffset:187;transform:rotate(450deg)}}.confirm-box-wrapper,.confirm-box-wrapper .mask{position:absolute;top:0;left:0;right:0;bottom:0}.confirm-box-wrapper .mask{background:rgba(0,0,0,.6)}.confirm-box-wrapper .confirm-box{position:fixed;top:50%;left:50%;width:267px;background:#fff;transform:translate(-50%,-50%);border-radius:7px}.confirm-box-wrapper .confirm-box .head{margin:0 18px;font-size:18px;text-align:center;line-height:65px;border-bottom:1px solid #d9d9d9}.confirm-box-wrapper .confirm-box .body{padding:18px;padding-bottom:0;color:#353535;font-size:12.5px;max-height:150px;overflow:auto}.confirm-box-wrapper .confirm-box .foot{display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;padding:18px}.confirm-box-wrapper .confirm-box .foot .button-cancel{border:1px solid #d9d9d9}.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}




    </style>
<style type="text/css">
        .button-cancel[data-v-87ffcada]{color:#888;border:1px solid #888;border-radius:3px;margin-right:12px}.button-cancel[data-v-87ffcada],.button-primary[data-v-87ffcada]{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;height:35px;display:inline-block;font-size:15px;text-align:center;line-height:36px}.button-primary[data-v-87ffcada]{color:#fff;background-color:#ff5a05;border-radius:3px}.pd[data-v-87ffcada]{padding-left:1.375rem;padding-right:1.375rem}.article[data-v-87ffcada]{max-width:70rem;margin:0 auto}.article .article-unavailable[data-v-87ffcada]{color:#fa8919;font-size:15px;font-weight:600;line-height:24px;border-radius:5px;padding:12px;background-color:#f6f7fb;margin-top:20px}.article .article-unavailable .iconfont[data-v-87ffcada]{font-size:12px}.article .main[data-v-87ffcada]{padding:1.25rem 0;margin-bottom:52px}.article-title[data-v-87ffcada]{color:#353535;font-weight:400;line-height:1.65rem;font-size:1.34375rem}.article-info[data-v-87ffcada]{color:#888;font-size:.9375rem;margin-top:1.0625rem}.article-content[data-v-87ffcada]{margin-top:1.0625rem}.article-content.android video[data-v-87ffcada]::-webkit-media-controls-fullscreen-button{display:none}.copyright[data-v-87ffcada]{color:#b2b2b2;padding-bottom:20px;margin-top:20px;font-size:13px}.audio-player[data-v-87ffcada]{width:100%;margin:20px 0}.to-comment[data-v-87ffcada]{overflow:hidden;padding-top:10px;margin-bottom:-30px}.to-comment a.button-primary[data-v-87ffcada]{float:right;height:20px;font-size:12px;line-height:20px;padding:4px 8px;cursor:pointer}.article-comments[data-v-87ffcada]{margin-top:2rem}.article-comments h2[data-v-87ffcada]{text-align:center;color:#888;position:relative;z-index:1;margin-bottom:1rem}.article-comments h2[data-v-87ffcada]:before{border-top:1px dotted #888;content:"";position:absolute;top:56%;left:0;width:100%;z-index:-1}.article-comments h2 span[data-v-87ffcada]{font-size:15.25px;font-weight:400;padding:0 1rem;background:#fff;display:inline-block}.article-sub-bottom[data-v-87ffcada]{z-index:10;cursor:pointer}.switch-btns[data-v-87ffcada]{height:76px;cursor:pointer;padding-top:24px;padding-bottom:24px;border-bottom:10px solid #f6f7fb;position:relative}.switch-btns[data-v-87ffcada]:before{content:" ";height:1px;background:#e8e8e8;position:absolute;top:0;left:0;-webkit-box-sizing:border-box;box-sizing:border-box;left:1.375rem;right:1.375rem}.switch-btns .btn[data-v-87ffcada]{height:38px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.switch-btns .btn .tag[data-v-87ffcada]{-webkit-box-flex:0;-ms-flex:0 0 62px;flex:0 0 62px;text-align:center;color:#888;font-size:14px;border-radius:10px;height:22px;line-height:22px;background:#f6f7fb;font-weight:400}.switch-btns .btn .txt[data-v-87ffcada]{margin-left:10px;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;color:#888;font-size:15px;height:22px;line-height:22px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400}@media (max-width:769px){.article .breadcrumb[data-v-87ffcada]{padding-top:10px;padding-bottom:10px}}





    </style>
<style type="text/css">
        .comment-item{list-style-position:inside;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;margin-bottom:1rem}.comment-item a{border-bottom:none}.comment-item .avatar{width:2.625rem;height:2.625rem;-ms-flex-negative:0;flex-shrink:0;border-radius:50%}.comment-item .info{margin-left:.5rem;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.comment-item .info .hd{width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.comment-item .info .hd .username{color:#888;font-size:15.25px;font-weight:400;line-height:1.2}.comment-item .info .hd .control{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.comment-item .info .hd .control .btn-share{color:#888;font-size:.75rem;margin-right:1rem}.comment-item .info .hd .control .btn-praise{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:15.25px;text-decoration:none}.comment-item .info .hd .control .btn-praise i{color:#888;display:inline-block;font-size:.75rem;margin-right:.3rem;margin-top:-.01rem}.comment-item .info .hd .control .btn-praise i.on,.comment-item .info .hd .control .btn-praise span{color:#ff5a05}.comment-item .info .bd{color:#353535;font-size:15.25px;font-weight:400;white-space:normal;word-break:break-all;line-height:1.6}.comment-item .info .time{color:#888;font-size:9px;line-height:1}.comment-item .info .reply .reply-hd{font-size:15.25px}.comment-item .info .reply .reply-hd span{margin-left:-12px;color:#888;font-weight:400}.comment-item .info .reply .reply-hd i{color:#ff5a05;font-size:15.25px}.comment-item .info .reply .reply-content{color:#353535;font-size:15.25px;font-weight:400;white-space:normal;word-break:break-all}.comment-item .info .reply .reply-time{color:#888;font-size:9px}




    </style>
</head>
<body>
<div id="app">
<div class="article" data-v-87ffcada="" id="watermark">

<div class="main main-app" data-v-87ffcada="">
<h1 class="article-title pd" data-v-87ffcada="">
                36讲插件开发（五）：工作台相关API
            </h1>
<div class="article-content typo common-content pd" data-v-87ffcada=""><img data-v-87ffcada="" src="https://static001.geekbang.org/resource/image/c7/0a/c7e8447ca5fdca13b6722802ac72e00a.jpg"/>
<div class="" data-v-87ffcada="" id="article-content">
<div class="text">
<p>在插件部分的前几篇文章，我一直在介绍编辑器相关的 API。不过，除了拓展编辑器以外，我们还可以拓展 VS Code 的其他组件，这一类 API，我把它们叫做<strong>工作台 API</strong>。下面我们就来看看， 工作台相关的 API 有哪些种类，以及我们可以如何使用它们。</p><p>今天，我们依然使用 JavaScript 插件模板。</p><h2>一、信息提示和 QuickPick</h2><p>第一类，就是通过插件 API 在 VS Code 中调出对话框向用户询问问题，或者弹出信息提示以警示用户。其实这二者的本质都是一致的，都是跟用户进行信息的交互，以完成进一步的操作。</p><h3>1、Information、Warning和Error 消息</h3><p>首先我们来看看信息提示，这个 API 我们在之前的例子里已经使用过了。</p><p>下面的这段示例代码，我们注册了 extension.sayHello 这个命令，这个命令通过 vscode.window.showInformationMessage API，输出了一个提示 “Hello World”：</p><pre><code>vscode.commands.registerCommand('extension.sayHello', () =&gt; {
    vscode.window.showInformationMessage('Hello World');
});
</code></pre><p>代码运行的效果如下：</p><p><img alt="" src="https://static001.geekbang.org/resource/image/75/d7/75680c0270c428e389bf17831e3071d7.gif"/></p><center><span class="reference">showInformationMessage</span></center><p>然后我们再来看看对话框。这里我们可以使用的 API 还有两种：showWarningMessage 和 showErrorMessage。使用方法都是一样的，不过呈现效果会不同，以体现 Information、Warning和Error 不同的重要程度。</p><!-- [[[read_end]]] --><p><img alt="" src="https://static001.geekbang.org/resource/image/3a/d2/3a85bc1eedccaa903e337134a08a34d2.png"/></p><center><span class="reference">showWarningMessage</span></center><p><img alt="" src="https://static001.geekbang.org/resource/image/1a/cf/1a0643e3bbdaa40d597e9fde20d5facf.png"/></p><center><span class="reference">showErrorMessage</span></center><p>除了给用户展示信息以外，这三个 API 还允许我们和用户进行简单的交互。下面我们来看看 showInformationMessage 的定义：</p><pre><code>/**
 * Show an information message to users. Optionally provide an array of items which will be presented as
 * clickable buttons.
 *
 * @param message The message to show.
 * @param items A set of items that will be rendered as actions in the message.
 * @return A thenable that resolves to the selected item or `undefined` when being dismissed.
 */
export function showInformationMessage(message: string, ...items: string[]): Thenable&lt;string | undefined&gt;;
</code></pre><p>除了传入消息 message，我们还可以传入一个数组，这个数组里的字符串，都会被渲染成按钮，当用户按下这些按钮，我们就能够收到反馈了。下面，我们不妨把样例代码修改成：</p><pre><code>vscode.commands.registerCommand('extension.sayHello', () =&gt; {
    vscode.window.showInformationMessage('Hello World', 'Yes', 'No').then(value =&gt; {
        vscode.window.showInformationMessage('User press ' + value);
    })
});
</code></pre><p>我们给用户提供了 “Yes” 和 “No” 两个选项，当用户选择其中之一后，弹出一个新的信息框，显示用户点击了哪个。</p><p><img alt="" src="https://static001.geekbang.org/resource/image/39/99/39759c3d17c9fc4841310c4b44407c99.gif"/></p><center><span class="reference">showInformationMessage 用户操作选项 </span></center><h3>2、QuickPick</h3><p>接下来这个 API 就是 QuickPick 了，通过 <code>vscode.window.showQuickPick</code> 这个函数，给用户提供了一系列选项，然后根据用户选择的选项进行下一步的操作。比如下面的这段代码：</p><pre><code>vscode.window.showQuickPick(['first', 'second', 'third']).then(value =&gt; {
   vscode.window.showInformationMessage('User choose ' + value);
})
</code></pre><p>我们给用户提供了三个选项 ：‘first’、‘second’、‘third’，然后将用户的选择以信息的方式弹出。</p><p><img alt="" src="https://static001.geekbang.org/resource/image/fd/bd/fd91a443af4ffbcaa938876180892cbd.gif"/></p><center><span class="reference">QuickPick选项</span></center><p>showQuickPick API 的第一个参数除了可以是一个字符串数组以外，还可以提供其他不同的类型。</p><p>比如<strong>Promise</strong>，这个参数可以为最终解析值为字符串数组的 Promise。有了这个类型，我们就能够异步地获取选项列表，等这个列表解析出来了再提供给用户，而用户则会在界面上看到滚动条。</p><p>另外，这个数组也可以是 <strong>QuickPickItem</strong> 对象数组。QuickPickItem 的结构为：</p><pre><code>export interface QuickPickItem {
 /**
  * A human readable string which is rendered prominent.
  */
 label: string;
 description?: string;
 detail?: string;
 picked?: boolean;
 alwaysShow?: boolean;
}
</code></pre><p>其实，我们在上面的示例里面使用的数组，也可以用 QuickPickItem 来替代，我们只需要使用 QuickPickItem 的 label 属性，然后label 里的值就会被渲染在列表中。</p><p>除了 label，我们还可以通过 description 或者 detail 来提供更多的信息。比如说，如果我们使用下面的 QuickPickItem 数组：</p><pre><code>vscode.commands.registerCommand('extension.sayHello', () =&gt; {
    vscode.window.showQuickPick([{
        label: 'first',
        description: 'first item',
        detail: 'first item details'
    }, {
        label: 'second',
        description: 'second item',
        detail: 'second item details'
    }]).then(value =&gt; {
        vscode.window.showInformationMessage('User choose ' + value.label);
    })
});
</code></pre><p>我们则会得到如下图所示的列表：</p><p><img alt="" src="https://static001.geekbang.org/resource/image/35/db/35540769fef712a907965edfabe026db.png"/></p><center><span class="reference">QuickPickItem</span></center><p>至于picked 这个属性，就非常好理解了。默认情况下列表里的第一个选项会被选中。如果你希望默认选中其他项的话，将它的 picked 属性改为 true 就好了。alwaysShow 这个属性则是使用于列表很长的情况，如果列表非常长，VS Code 不得不渲染出滚动条时，那么通过将某些项的 alwaysShow属性改为 true，这个选项就会一直出现在列表中，而不会受滚动条的影响。</p><p>整体来讲，在我们实现插件的过程中，很多命令或者操作的流程、信息，并不是完全确定的，我们往往需要用户来提供更多的信息，并且由用户来做出最终的决定。这个时候，通过信息提示和 QuickPick，我们将选择权交还给了用户。</p><p>但是一定要注意的是：信息提示和 QuickPick都是会打扰用户的正常工作。所以我们在使用这类 API 的时候一定要慎重，不然用户可能就会卸载我们的插件了。</p><h2>二、面板 Panel</h2><p>第二类就是面板里的信息了。默认情况下，面板中有以下几个组件：</p><ul>
<li>问题面板</li>
<li>调试面板</li>
<li>输出面板</li>
<li>终端面板</li>
</ul><p>除了调试面板是由调试插件控制的以外，其他的三个，都是可以通过普通的插件 API 来完成。而这里面，又属问题面板和输出面板使用最为频繁，所以我们着重来看看这二者的 API。</p><h3>1、问题面板</h3><p>当我们在书写代码时，VS Code 的各类插件，会把代码中出现的错误信息提供给问题面板。然后用户就可以通过问题面板，快速地查询问题并且进行代码的跳转。</p><p>问题面板相关的 API 存在于 vscode.languages 这个 namespace 下。要给问题面板提供相关的信息，我们要使用的 API 是 createDiagnosticCollection。</p><pre><code>export namespace languages {
  /**
   * Create a diagnostics collection.
   *
   * @param name The <a href="#DiagnosticCollection.name">name</a> of the collection.
   * @return A new diagnostic collection.
   */
  export function createDiagnosticCollection(name?: string): DiagnosticCollection;
}
</code></pre><p>通过 vscode.languages.createDiagnosticCollection 创建出来的对象，就将是我们跟 VS Code 问题面板通讯的中介。</p><p>下面，我们使用如下的代码样例进行讲解。</p><pre><code>vscode.commands.registerCommand('extension.sayHello', () =&gt; {
    let collection = vscode.languages.createDiagnosticCollection('myextension');
    let uri = vscode.window.activeTextEditor.document.uri;
    collection.set(uri, [
        {
            range: new vscode.Range(0, 0, 0, 1),
            message: 'We found an error'
        }
    ]);
});
</code></pre><p>在 collection 对象创建出来后，接着我们就要往这个 collection 里塞数据，这里要使用的 API 是 <code>set(uri: Uri, diagnostics: Diagnostic[] | undefined): void;</code> 。</p><p><img alt="" src="https://static001.geekbang.org/resource/image/3f/a9/3fdc268efcb05cebd15061b59f55d8a9.png"/></p><center><span class="reference">collection.set </span></center><p>set 函数提供两个参数：第一个就是文档的地址 Uri，样例代码里使用了 vscode.window.activeTextEditor.document.uri ，也就是当前编辑器里的文档的地址 Uri；第二个就是我们在这个文档里发现的所有问题，每个问题的类型必须是 Diagnostic。</p><pre><code>export class Diagnostic {
 /**
  * The range to which this diagnostic applies.
  */
 range: Range;
 /**
  * The human-readable message.
  */
 message: string;
 /**
  * The severity, default is <a href="#DiagnosticSeverity.Error">error</a>.
  */
 severity: DiagnosticSeverity;

 source?: string;
 code?: string | number;
 relatedInformation?: DiagnosticRelatedInformation[];
 tags?: DiagnosticTag[];
 constructor(range: Range, message: string, severity?: DiagnosticSeverity);
}
</code></pre><p><strong>Diagnostic 对象必须要提供的两个属性就是 range 和 message，也就是问题所在的位置和问题相关的信息。</strong>我们还可以给 Diagnostic 对象提供诸如 severity 问题的程度、source 问题的来源等。</p><p>当我们把上面的代码运行起来后，在编辑器里执行 “Hello World” 命令，可以看到第一行第一列代码下出现了波浪线，同时问题面板里也多出了一个条目，点击它就能够跳转到编辑器中。</p><p><img alt="" src="https://static001.geekbang.org/resource/image/0b/c9/0bd7e4d5af1a25e78d3f2825f8b598c9.gif"/></p><center><span class="reference">输出内容至问题面板</span></center><h3>2、输出面板</h3><p>相比于问题面板，为输出面板提供内容的 API 要更简单一些。这一次，我们首先要创建一个 OutputChannel：</p><pre><code>let channel = vscode.window.createOutputChannel('MyExtension');
</code></pre><p>我们只要提供一个名字即可。接着，我们就可以往这个对象中添加输出日志了：</p><pre><code>channel.appendLine('Hello World');
</code></pre><p>有了这两行代码，就可以运行了。</p><p><img alt="" src="https://static001.geekbang.org/resource/image/5c/ff/5ca40b57e74328d9c8abfdde4e40d1ff.gif"/></p><center><span class="reference">输出内容至输出面板</span></center><p>通过上面的代码你可以发现，输出面板下拉框中现在出现了一个新的选项，叫做<strong>MyExtension</strong>，也就是我们创建的 OutputChannel。接着我们使用 channel.appendLine 输出的信息，就会被放在输出面板中。这套 API 非常像 console.log()，唯一不同的是，这套 API 将内容输出到了输出面板中。</p><p>这部分总体来说就是：问题面板的使用，跟语言服务结合到一起会很好，比如 Linting 信息、编译错误信息，甚至错别字检查信息，都可以塞到问题面板中。不过要注意，问题面板里的内容，意味着需要用户去修改代码。所以一些无关紧要的信息就不要放到这里面了。</p><p>而输出面板，大家完全可以把它当 log 日志来使用。大部分时间用户不需要去关心它，不过当用户遇到问题了，如果能够通过输出日志里的信息获得帮助，那么输出面板的目的就达到了。</p><h2>三、视图 TreeView</h2><p>第三类 API 则是跟视图有关。这一套 API 的最初需求是来自于 Visual Studio 用户，在 Visual Studio 中，我们可以在视图中看到项目、测试、云管理等，但是 VS Code 当时并没有 API 可以实现这种定制。于是 TreeView API 应运而生，通过实现这套 API，任何插件都可以实现类似于资源管理器的树形结构。</p><p>TreeView API 虽然是用于创建视图中树形结构的，但是它跟 VS Code 的其他 API 非常类似，都是给 VS Code 提供数据，然后 VS Code 来进行渲染。创建 TreeView 的 API 也非常简单：</p><pre><code>export namespace window {
 export function registerTreeDataProvider&lt;T&gt;(viewId: string, treeDataProvider: TreeDataProvider&lt;T&gt;): Disposable;
}
</code></pre><p>registerTreeDataProvider 一共有两个参数：第一个是这个 TreeView 的名字，第二个则是这个 TreeView 的数据来源 Data Provider。那么这个 Data Provider 长什么样呢？</p><pre><code> export interface TreeDataProvider&lt;T&gt; {
  onDidChangeTreeData?: Event&lt;T | undefined | null&gt;;
  getTreeItem(element: T): TreeItem | Thenable&lt;TreeItem&gt;;
  getChildren(element?: T): ProviderResult&lt;T[]&gt;;
  getParent?(element: T): ProviderResult&lt;T&gt;;
 }
</code></pre><p>这个 Data Provider 上，只有两个属性是必须的。第一个是 <strong>getTreeItem</strong>，通过这个函数，VS Code 就知道该怎么渲染某个树节点了。第二个就是 <strong>getChildren</strong>，这个也很好理解，就是返回一个树节点的所有子节点的数据。在介绍样例代码之前，我们再看看 TreeItem，也就是每个树节点的数据结构：</p><pre><code> export class TreeItem {
  label?: string;
  id?: string;
  iconPath?: string | Uri | { light: string | Uri; dark: string | Uri } | ThemeIcon;
  resourceUri?: Uri;
  tooltip?: string | undefined;

  /**
   * The <a href="#Command">command</a> that should be executed when the tree item is selected.
   */
  command?: Command;

  /**
   * <a href="#TreeItemCollapsibleState">TreeItemCollapsibleState</a> of the tree item.
   */
  collapsibleState?: TreeItemCollapsibleState;
  contextValue?: string;
  constructor(label: string, collapsibleState?: TreeItemCollapsibleState);
  constructor(resourceUri: Uri, collapsibleState?: TreeItemCollapsibleState);
 }
</code></pre><p>TreeItem 有两种创建方式：第一种，就是提供 label，也就是一个字符串，VS Code 会把这个字符串渲染在树形结构中；第二种就是提供 resourceUri，也就是一个资源地址，VS Code 则会像资源管理器里渲染文件和文件夹一样渲染这个节点的。</p><p>至于其他属性：</p><ul>
<li>
<p>iconPath 属性，是用于控制树节点前的图标的。如果说我们自己通过 TreeView  API 来实现一个资源管理器的话，就可以使用 iconPath 来为不同的文件类型指定不同的图标。</p>
</li>
<li>
<p>tooltip 属性，当我们把鼠标移动到某个节点上等待片刻，VS Code 就会显示出这个节点对应的 tooltip 文字。</p>
</li>
<li>
<p>collapsibleState 是用于控制这个树节点是应该展开还是折叠。当然，如果这个节点没有子节点的话，这个属性就用不着了。</p>
</li>
<li>
<p>command 属性，如果有这个属性的话，当我们点击这个树节点时，这个属性所指定的命令就会被执行了。</p>
</li>
</ul><p>了解了以上几个属性，就能够实现一个简易的 TreeView 了。下面我们就来看看一段示例代码：</p><pre><code>vscode.window.registerTreeDataProvider('myextension', {
    getChildren: (element) =&gt; {
        if (element) {
            return null;
        }

        return ['first', 'second', 'third'];
    },
    getTreeItem: (element) =&gt; {
        return {
            label: element,
            tooltip: 'my ' + element + ' item'
        }
    }
})
</code></pre><p>上面的这段代码，注册了一个名为 myextension 的 TreeView，这个 TreeView 只有一层节点，它们分别是 ‘first’、 ‘second’、 ‘third’。</p><p>当我们将这段代码放入 extension.js 中时，运行插件，你会发现，VS Code 的视图里找不到这个名为 myextension 的 TreeView。这是为什么呢？</p><pre><code>const vscode = require('vscode');

function activate(context) {
    vscode.window.registerTreeDataProvider('myextension', {
        getChildren: (element) =&gt; {
            if (element) {
                return null;
            }

            return ['first', 'second', 'third'];
        },
        getTreeItem: (element) =&gt; {
            return {
                label: element,
                tooltip: 'my ' + element + ' item'
            }
        }
    })
}
exports.activate = activate;

function deactivate() {
}
exports.deactivate = deactivate;
</code></pre><p>不知道你还记不记得，之前我们在 extension.js 里创建 extension.sayHello 命令，同时，我们还在 package.json 的 contributes 部分申明了这个命令。没错，<strong>要想将这个 TreeView 成功地注册到 VS Code 中，你还得在 package.json 的 contributes 部分添加 TreeView 的申明</strong>。修改后的 package.json 的 contributes 部分如下：</p><pre><code>{
    "contributes": {
        "views": {
   "explorer": [
    {
     "id": "myextension",
     "name": "My Extension"
                }
            ]
        }
    }
}
</code></pre><p>这段 contributes 是说，我们把 myextension 这个 TreeView  注册到资源管理器中。好了，下面我们运行这个插件：</p><p><img alt="" src="https://static001.geekbang.org/resource/image/68/da/6812907f1395074e8552e1e97e39aada.gif"/></p><center><span class="reference">将 TreeView 注册到资源管理器中</span></center><p>除了将 TreeView 注册到资源管理器 Explorer 下以外，我们也可以将它注册到版本管理视图中，对应的 contributes 如下：</p><pre><code>"contributes": {
    "views": {
        "scm": [
            {
                "id": "myextension",
                "name": "My Extension"
            }
        ]
    }
}
</code></pre><p>代码运行起来后，我们就能够在版本管理视图中看到这个 TreeView 了。</p><p><img alt="" src="https://static001.geekbang.org/resource/image/89/cc/89705a4f10a27025e06201c7f973b1cc.gif"/></p><center><span class="reference">将 TreeView 注册到版本管理视图中</span></center><p>简言之，VS Code 的 TreeView API 使用了 Data Provider 的模式，插件提供数据，而 VS Code 负责渲染。至于数据长什么样、树形结构里的层级关系如何，这个就属于 Business Logic 了，需要大家发挥想象力。比如说 GitHub Pull Request 插件，用树形结构来展示所有的 Pull Requests 和每个 PR 里的代码改动，NPM Explorer 则将所有的 NPM 脚本展示在树形结构中。你也可以想想有什么别的有用的信息可以放在视图中呢？</p><h2>小结</h2><p>好了，以上就是今天内容的全部了。今天我们介绍了三类 API：信息提示和QuickPick，面板类 API， 以及视图 TreeView。</p><p>除此之外，VS Code 还有很多别的有趣的工作台相关的 API，比如你可以使用 WebView API 来生成任意的编辑器内容，可以使用 FileSystemProvider 或者 TextDocumentContentProvider 来为 VS Code 提供类似于本地文件的文本内容。虽然它们很小众也更高级，但是使用的方法，跟上面提到的几种并没有什么区别，建议你通过 VS Code 的 typings 文件找寻你想要使用的 API，多多尝试。</p><hr/><p><img alt="" src="https://static001.geekbang.org/resource/image/92/06/92862660523add24b3168f22954fa506.jpg"/></p>
</div>
</div>
</div>
<div class="article-comments pd" data-v-87ffcada=""><h2 data-v-87ffcada=""><span data-v-87ffcada="">精选留言</span></h2>
<ul data-v-87ffcada="">
<li class="comment-item" data-v-87ffcada=""><img class="avatar" src="https://static001.geekbang.org/account/avatar/00/12/c5/85/2651f4dd.jpg"/>
<div class="info">
<div class="hd"><span class="username">谢mingmin</span>
</div>
<div class="bd">vs code的扩展API文档感觉目录结构很乱啊，有考虑优化一下结构吗？ <br/></div>
<span class="time">2018-12-04 07:52</span>
</div>
</li>
<li class="comment-item" data-v-87ffcada=""><img class="avatar" src="https://static001.geekbang.org/account/avatar/00/0f/74/86/abb7bfe3.jpg"/>
<div class="info">
<div class="hd"><span class="username">吉泓铭</span>
</div>
<div class="bd">可以增加一些应用场景的列举吗。 <br/></div>
<span class="time">2018-12-04 11:50</span>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
</html>